package com.zhx.common.user.service.impl;

import com.zhx.common.common.annotation.RedissonLock;
import com.zhx.common.common.event.UserBlackEvent;
import com.zhx.common.common.event.UserRegisterEvent;
import com.zhx.common.common.util.AssertUtil;
import com.zhx.common.user.dao.BlackDao;
import com.zhx.common.user.dao.ItemConfigDao;
import com.zhx.common.user.dao.UserBackpackDao;
import com.zhx.common.user.dao.UserDao;
import com.zhx.common.user.domain.dto.ItemInfoDTO;
import com.zhx.common.user.domain.dto.SummeryInfoDTO;
import com.zhx.common.user.domain.entity.*;
import com.zhx.common.user.domain.enums.BlackTypeEnum;
import com.zhx.common.user.domain.enums.ItemEnum;
import com.zhx.common.user.domain.enums.ItemTypeEnum;
import com.zhx.common.user.domain.vo.req.user.ItemInfoReq;
import com.zhx.common.user.domain.vo.req.user.SummeryInfoReq;
import com.zhx.common.user.domain.vo.resp.user.BadgeResp;
import com.zhx.common.user.domain.vo.resp.user.UserInfoResp;
import com.zhx.common.user.service.IUserService;
import com.zhx.common.user.service.adapter.UserAdapter;
import com.zhx.common.user.service.cache.ItemCache;
import com.zhx.common.user.service.cache.UserCache;
import com.zhx.common.user.service.cache.UserSummaryCache;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


@Service
@Slf4j
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserCache userCache;

    @Resource
    private UserDao userDao;

    @Resource
    private UserBackpackDao userBackpackDao;

    @Resource
    private ItemCache itemCache;

    @Resource
    private ItemConfigDao itemConfigDao;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private UserSummaryCache userSummaryCache;

    @Resource
    private BlackDao blackDao;

    @Override
    @Transactional
    public Long register(User insert) {
        userDao.save(insert);
        //这里注册后直接就发徽章不就行了，为什么要发布一个事件呢？
        //一是可以解耦，二是因为加了事务注解，可以决定是否和其在同一个事务中执行

        //发布用户注册的事件
        //事件的订阅者想知道这个事件是从哪里来的，所以需要传递一个事件源对象-》this
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, insert));
        return insert.getId();
    }

    @Override
    public UserInfoResp getUserInfo(Long uid) {
        User user = userDao.getById(uid);
        //获得用户改名卡次数
        Integer modifyNameCount = userBackpackDao.getCountByUid(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        return UserAdapter.builderUserInfo(user, modifyNameCount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedissonLock(key = "#uid")
    public void modifyName(Long uid, String name) {
        //1.判断名称是否被占用
        User oldUser = userDao.getByName(name);
        AssertUtil.isNotEmpty(oldUser, "名字重复，换个名字吧");

        //2.判断是否还有改名卡
        UserBackpack modifyNameItem = userBackpackDao.getFirstValidItem(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        AssertUtil.isNotEmpty(modifyNameItem, "没有改名卡了");

        //3.使用改名卡
        boolean b = userBackpackDao.userItem(modifyNameItem);
        AssertUtil.isTrue(b, "使用改名卡失败");

        //4.修改用户名
        boolean b1 = userDao.modifyName(uid, name);
        AssertUtil.isTrue(b1, "修改用户名失败");
    }

    @Override
    public List<BadgeResp> budges(Long uid) {
        //1.查询所有的徽章
        List<ItemConfig> itemConfigs = itemCache.getByType(ItemTypeEnum.BADGE.getType());

        //2.查询用户拥有的徽章
        List<UserBackpack> backpacks = userBackpackDao.getByItemIds(uid, itemConfigs.stream().map(ItemConfig::getId).collect(Collectors.toList()));

        //3.用户当前佩戴的徽章
        User user = userDao.getById(uid);

        //4.组装徽章
        return UserAdapter.builderBadgeResp(itemConfigs, backpacks, user);
    }

    @Override
    public void wearBadge(Long uid, Long badgeId) {
        //1.确保有该物品
        UserBackpack firstValidItem = userBackpackDao.getFirstValidItem(uid, badgeId);
        AssertUtil.isNotEmpty(firstValidItem, "没有该徽章");

        //2.确保该物品是徽章
        ItemConfig itemConfig = itemConfigDao.getById(badgeId);
        AssertUtil.equal(itemConfig.getType(), ItemTypeEnum.BADGE.getType(), "只有徽章才能佩戴");

        //3.佩戴徽章
        userBackpackDao.wearingBadge(uid, badgeId);
    }

    //拉黑用户
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void black(Long blackUid) {
        //拉黑用户uid
        Black black = new Black();
        black.setType(BlackTypeEnum.UID.getType());
        black.setTarget(blackUid.toString());
        blackDao.save(black);

        User user = userDao.getById(blackUid);
        //拉黑用户ip
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getCreateIp).orElse(null));
        blackIp(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getUpdateIp).orElse(null));

        //发布拉黑用户事件
        applicationEventPublisher.publishEvent(new UserBlackEvent(this, user));
    }

    @Override
    public List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoReq req) {
        //需要前端同步的uid
        List<Long> uidList = getNeedSyncUidList(req.getReqList());
        //加载用户信息
        Map<Long, SummeryInfoDTO> batch = userSummaryCache.getBatch(uidList);
        return req.getReqList()
                .stream()
                .map(a -> batch.containsKey(a.getUid()) ? batch.get(a.getUid()) : SummeryInfoDTO.skip(a.getUid()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 获取需要同步的用户ID列表。
     * 对传入的用户信息请求列表进行处理，筛选出那些需要进行数据同步的用户ID。
     * 需要同步的判断条件是用户信息的最后修改时间在缓存中的记录时间晚于或等于  请求中指定的最后修改时间。
     *
     * @param reqList 用户信息请求列表，包含每个用户ID及其最后一次修改时间。
     * @return 需要进行数据同步的用户ID列表。
     */
    private List<Long> getNeedSyncUidList(List<SummeryInfoReq.infoReq> reqList) {
        List<Long> needSyncUidList = new ArrayList<>(); // 初始化需要同步的用户ID列表
        // 从用户缓存中获取指定用户ID的最后修改时间列表
        List<Long> userModifyTime = userCache.getUserModifyTime(reqList.stream().map(SummeryInfoReq.infoReq::getUid).collect(Collectors.toList()));
        for (int i = 0; i < reqList.size(); i++) {
            SummeryInfoReq.infoReq infoReq = reqList.get(i);
            Long modifyTime = userModifyTime.get(i);
            // 判断是否需要同步该用户的信息：若请求中没有提供最后修改时间，或缓存中的最后修改时间晚于请求中的最后修改时间，则需同步
            if (Objects.isNull(infoReq.getLastModifyTime()) || (Objects.nonNull(modifyTime) && modifyTime > infoReq.getLastModifyTime())) {
                needSyncUidList.add(infoReq.getUid());
            }
        }
        return needSyncUidList;
    }


    @Override
    public List<ItemInfoDTO> getItemInfo(ItemInfoReq req) {
        return req.getReqList().stream().map(a -> {
            ItemConfig itemConfig = itemCache.getById(a.getItemId());
            if (Objects.nonNull(a.getLastModifyTime()) && a.getLastModifyTime() >= itemConfig.getUpdateTime().getTime()) {
                return ItemInfoDTO.skip(a.getItemId());
            }
            ItemInfoDTO dto = new ItemInfoDTO();
            dto.setItemId(itemConfig.getId());
            dto.setImg(itemConfig.getImg());
            dto.setDescribe(itemConfig.getDescribe());
            return dto;
        }).collect(Collectors.toList());
    }

    //拉黑ip
    private void blackIp(String ip) {
        if(StringUtil.isEmpty(ip)) {
            return;
        }
        try{ //有可能createIp和updateIp相等造成数据库插入相同的数据失败
            Black black = new Black();
            black.setType(BlackTypeEnum.IP.getType());
            black.setTarget(ip);
            blackDao.save(black);
        }catch (Exception e) {
            log.error(e.getMessage());
        }

    }
}
